#include "share_mem.h"

#define ARRAY_SIZE(arr)         (sizeof(arr) / sizeof((arr)[0]))

MEMCFG mapCfg = {
    .mem_fd = -1,
    .mem_base_tab = {
        {.map_addr=0x10100000, .size=0x100000,   .i_prop = PROT_READ | PROT_WRITE, .mem_base = 0}
    } 
};

/**
 * @brief 解除内存映射并释放资源
 * 
 * 该函数用于解除之前通过 `mmap` 或 `malloc` 分配的内存映射，
 * 并关闭打开的文件描述符，将相关指针置为 `NULL`，文件描述符置为 -1。
 * 
 * @param pMapCfg 指向 `MEMCFG` 结构体的指针，该结构体包含内存映射的相关信息。
 */
void unmapMem(MEMCFG *pMapCfg)
{
    int i;
    
    for(i=0; i<ARRAY_SIZE(pMapCfg->mem_base_tab); i++)
    {
        if(pMapCfg->mem_base_tab[i].mem_base)
        {
            munmap((void*)pMapCfg->mem_base_tab[i].mem_base, pMapCfg->mem_base_tab[i].size);
            pMapCfg->mem_base_tab[i].mem_base = NULL;
        }      
    }

    if(pMapCfg->mem_fd >= 0)
    {
        close(pMapCfg->mem_fd);
        pMapCfg->mem_fd = -1;
    }
}

/**
 * @brief 映射内存区域
 * 
 * 该函数用于将指定地址和大小的内存区域映射到进程的地址空间。
 * 根据 `TEST` 宏的定义，在测试模式下使用 `malloc` 分配内存，
 * 非测试模式下使用 `mmap` 进行内存映射。
 * 
 * @param pMapCfg 指向 `MEMCFG` 结构体的指针，用于存储内存映射信息。
 * @param addr 要映射的内存区域的起始物理地址。
 * @param size 要映射的内存区域的大小。
 * @param prop 内存映射的保护属性，如 `PROT_READ`、`PROT_WRITE` 等。
 * @param index `mem_base_tab` 数组中的索引，用于存储映射结果。
 * @return int 映射成功返回 0，失败返回 -1。
 */
static int mapMem(MEMCFG *pMapCfg, off_t addr, size_t size, int prop, int index)
{
    off_t pa_read_offset = addr & ~(sysconf(_SC_PAGE_SIZE) - 1);

    size = size + addr - pa_read_offset;

    pMapCfg->mem_base_tab[index].size = size;

    pMapCfg->mem_base_tab[index].mem_base = (uint8_t *)mmap(NULL, size, prop, MAP_SHARED, pMapCfg->mem_fd, pa_read_offset);

    if ((void*)pMapCfg->mem_base_tab[index].mem_base == MAP_FAILED)
    {
        pMapCfg->mem_base_tab[index].mem_base = NULL;
        printf("can_modbus mmap error\n");
        unmapMem(pMapCfg);
        return -1;
    }

    pMapCfg->mem_base_tab[index].map_addr = pa_read_offset;
    pMapCfg->mem_base_tab[index].i_prop = prop;

    return 0;
}

uint16_t *getDataAddrByAddr(int addr)
{
    // 初始化偏移量为0
    int offset = 0;

    // 遍历所有的共享内存区域
    for(int i=0; i<ARRAY_SIZE(mapCfg.mem_base_tab); i++)
    {
        // 计算当前区域的结束地址
        offset = mapCfg.mem_base_tab[i].size + (mapCfg.mem_base_tab[i].map_addr - mapCfg.mem_base_tab[0].map_addr);

        // 如果目标地址在当前区域内
        if(addr < offset )
        {
            // 计算目标地址在当前区域内的偏移量
            addr -= (mapCfg.mem_base_tab[i].map_addr - mapCfg.mem_base_tab[0].map_addr);
            // 返回指向共享内存中数据的指针
            return (uint16_t *)&mapCfg.mem_base_tab[i].mem_base[addr];
        }
    }

    // 如果没有找到合适的区域，返回0
    return 0;
}

int setDataParam(int addr,uint16_t data)
{
    // 初始化偏移量为0
    int offset = 0;

    // 遍历所有的共享内存区域
    for(int i=0; i< ARRAY_SIZE(mapCfg.mem_base_tab); i++)
    {
        // 计算当前区域的结束地址
        offset = mapCfg.mem_base_tab[i].size + (mapCfg.mem_base_tab[i].map_addr - mapCfg.mem_base_tab[0].map_addr);

        // 如果目标地址在当前区域内
        if(addr < offset )
        {
            // 检查当前区域是否可写
            if(!(mapCfg.mem_base_tab[i].i_prop & PROT_WRITE))
            {
                // 如果不可写，返回错误
                return -1;
            }

            // 计算目标地址在当前区域内的偏移量
            addr -= (mapCfg.mem_base_tab[i].map_addr - mapCfg.mem_base_tab[0].map_addr);
            
            if (addr % 2 != 0)
            {
                return -1;
            }

            // 将数据写入到目标地址
            *(uint16_t *)&mapCfg.mem_base_tab[i].mem_base[addr] = data;
            
            // 返回成功
            return 0;
        }
    }

    // 如果没有找到合适的区域，返回错误
    return -1;
}


static uint16_t getDataByAddr(int addr)
{
    if (addr % 2 != 0)
    {
        return 0xffff;
    }
    // 调用 getDataAddrByAddr 函数获取指定地址的指针
    uint16_t *pData = getDataAddrByAddr(addr);
    // 如果获取的指针为 NULL，则返回0xffff，否则返回指针指向的值
    return pData == NULL ? 0xffff : *pData;
}

int setBit(int  addr,int  dataType,int  bitIndex,int value)
{
    // 用于存储数据
    uint16_t data = 0;

    if (bitIndex < 0)
    {
        return -1;
    }

    switch(dataType)
    {
        case HES_UINT16:
        {
            // 如果位索引大于等于8，返回错误
            if(bitIndex >= 8)
            {
                return -1;
            }

            // 获取当前地址的数据，并将最高8位设置为序号
            data = getDataByAddr(addr) + 0x100;  

            // 清除低字节的所有位 低字节所有遥控bit互斥，只能有一个是1
            data = data & 0xFF00;                
            // 设置指定位的值
            data = data | (value << bitIndex);
            // 设置数据到指定地址
            return setDataParam(addr,data);
        }
        case HES_UINT32:
        {
            // 如果位索引大于等于16,返回错误
            if(bitIndex >= 16)
            {
                return -1;
            }

            // 获取当前地址的数据
            data = getDataByAddr(addr);

            data = data & (~(1 << bitIndex));

            data = data | (value << bitIndex);

            // 设置数据到指定地址
            setDataParam(addr,data);

            addr += 2;
            data = getDataByAddr(addr);
            data = data + 1;
            return setDataParam(addr,data);
        }
        default:
            break;
    }
    return 0; 
}


/**
 * @brief 初始化共享内存映射
 * 
 * 该函数用于初始化共享内存映射，根据 `TEST` 宏的定义，在非测试模式下会打开 `/dev/mem` 文件，
 * 然后遍历 `mem_base_tab` 数组，对每个内存区域调用 `mapMem` 函数进行映射。
 * 映射完成后，若文件描述符有效，则关闭文件描述符。
 * 
 * @param pMapCfg 指向 `MEMCFG` 结构体的指针，包含内存映射的相关配置信息。
 * @return int 初始化成功返回 0，失败返回 -1。
 */
int shareMemInit(MEMCFG *pMapCfg)
{
    int     i;
    off_t   i_addr;
    size_t  i_size;
    int     i_prop;
    
    pMapCfg->mem_fd = open("/dev/mem", O_RDWR | O_SYNC);

    if(pMapCfg->mem_fd < 0)
    {
       printf("mmslite open /dev/mem error!\n");
       return -1;
    }
             
    for(i = 0; i < ARRAY_SIZE(pMapCfg->mem_base_tab); i++)
    {
        i_addr = pMapCfg->mem_base_tab[i].map_addr;
        i_size = pMapCfg->mem_base_tab[i].size;
        i_prop = pMapCfg->mem_base_tab[i].i_prop;
        
        if( mapMem(pMapCfg, i_addr, i_size, i_prop, i) < 0)
        {
            return -1;
        }
    }

    if (pMapCfg->mem_fd >= 0)
    {
        close(pMapCfg->mem_fd);
        pMapCfg->mem_fd = -1;
    }

    return 0;
}

